home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 1.0 beta / flock-1.0RC3.en-US.win32.exe / flock / components / flockFacebookService.js < prev    next >
Text File  |  2007-10-18  |  71KB  |  2,095 lines

  1. // BEGIN FLOCK GPL
  2. // 
  3. // Copyright Flock Inc. 2005-2007
  4. // http://flock.com
  5. // 
  6. // This file may be used under the terms of of the
  7. // GNU General Public License Version 2 or later (the "GPL"),
  8. // http://www.gnu.org/licenses/gpl.html
  9. // 
  10. // Software distributed under the License is distributed on an "AS IS" basis,
  11. // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. // for the specific language governing rights and limitations under the
  13. // License.
  14. // 
  15. // END FLOCK GPL
  16.  
  17. const CC = Components.classes;
  18. const CI = Components.interfaces;
  19. const CR = Components.results;
  20.  
  21. Components.utils.import("resource://gre/modules/JSON.jsm");
  22.  
  23. const FACEBOOK_CID = Components.ID("{81a58e10-4f45-11db-b0de-0800200c9a66}");
  24. const FACEBOOK_CONTRACTID              = "@flock.com/people/facebook;1";
  25. const FACEBOOK_FAVICON                 = "http://www.facebook.com/favicon.ico";
  26. const FACEBOOK_TITLE                   = "Facebook Web Service";
  27. const SERVICE_ENABLED_PREF             = "flock.service.facebook.enabled";
  28. const CATEGORY_COMPONENT_NAME          = "Facebook JS Component";
  29. const CATEGORY_ENTRY_NAME              = "facebook";
  30. const FLOCK_RICH_CONTENT_CATEGORY_ENTRY = "flockRichContentHandler";
  31. const XPATH_WALLTEXT = "wallTextXPath";
  32.  
  33. const FLOCK_ACCOUNT_UTILS_CONTRACTID   = "@flock.com/account-utils;1";
  34. const FLOCK_ERROR_CONTRACTID           = "@flock.com/error;1";
  35. const FLOCK_PHOTO_ACCOUNT_CONTRACTID   = "@flock.com/photo-account;1";
  36. const FLOCK_PHOTO_ALBUM_CONTRACTID     = "@flock.com/photo-album;1";
  37. const FLOCK_PHOTO_API_MANAGER_CONTRACTID = "@flock.com/photo-api-manager;1?";
  38. const FLOCK_PHOTO_CONTRACTID           = "@flock.com/photo;1";
  39. const FLOCK_RDNDS_CONTRACTID           = "@flock.com/rich-dnd-service;1";
  40. const FLOCK_SINGLETON_CONTRACTID       = "@flock.com/singleton;1";
  41. const CATEGORY_MANAGER_CONTRACTID      = "@mozilla.org/categorymanager;1";
  42. const HASH_PROPERTY_BAG_CONTRACTID     = "@mozilla.org/hash-property-bag;1";
  43. const JS_SUBSCRIPT_LOADER_CONTRACTID   = "@mozilla.org/moz/jssubscript-loader;1";
  44. const OBSERVER_SERVICE_CONTRACTID      = "@mozilla.org/observer-service;1";
  45.  
  46. const FLOCK_SNOWMAN_URL = "chrome://browser/skin/flock/services/common/no_avatar.png";
  47. const FAVES_COOP = "chrome://flock/content/common/load-faves-coop.js";
  48. const FACEBOOK_PROPERTIES = "chrome://flock/locale/services/facebook.properties";
  49. const PEOPLE_PROPERTIES = "chrome://flock/locale/people/people.properties";
  50.  
  51. const FACEBOOK_IDENTITY_URN_PREFIX = "urn:flock:identity:facebook:";
  52.  
  53. const FACEBOOK_SHARE_FLOCK_SUBJECT = "flock.facebook.friendShareFlock.subject";
  54. const FACEBOOK_SHARE_FLOCK_MESSAGE = "flock.facebook.friendShareFlock.message";
  55.  
  56. // The first time, only get photos not older than one week
  57. const MEDIA_INITIAL_FETCH = 7 * 24 * 3600;
  58. // The delay between two refreshes when the sidebar is closed (in seconds)
  59. const FACEBOOK_REFRESH_INTERVAL = 1800; // 30 minutes
  60. // The delay between two refreshes when the sidebar is open (in seconds)
  61. const FACEBOOK_SHORT_INTERVAL = 300; // 5 minutes
  62.  
  63. // CONSTANTS FOR PHOTO UPLOADING
  64. // XXX This is a rough guess.  Facebook allows a max of 60 pictures per
  65. //     personal account, at a max file size of 5MB each.  Once there are API
  66. //     calls to actually get the space remaining, we should replace this.
  67. const FACEBOOK_MAX_PHOTO_SPACE = 60 * 5 * 1024 * 1024;
  68. const FACEBOOK_USED_PHOTO_SPACE = 0;
  69. // Facebook has a maximum file size of 5MB
  70. const FACEBOOK_MAX_FILE_SIZE = 5 * 1024 * 1024;
  71.  
  72. const CHAR_CODE_SPACE = " ".charCodeAt(0);
  73.  
  74. // ====================================================
  75. // ========== BEGIN General helper functions ==========
  76. // ====================================================
  77.  
  78. var gCompTK;
  79. function getCompTK() {
  80.   if (!gCompTK) {
  81.     gCompTK = CC[FLOCK_SINGLETON_CONTRACTID]
  82.               .getService(CI.flockISingleton)
  83.               .getSingleton("chrome://browser/content/flock/services/common/load-compTK.js")
  84.               .wrappedJSObject;
  85.   }
  86.   return gCompTK;
  87. }
  88.  
  89. // For use with the scheduler
  90. var gTimers = [];
  91.  
  92. // String defaults... may be updated later through Web Detective
  93. var gStrings = {
  94.   "domains": "facebook.com",
  95.   "userlogin": "http://www.facebook.com/login.php",
  96.   "userprofile": "http://www.facebook.com/profile.php?id=%accountid%"
  97. };
  98.  
  99. var _wm = CC["@mozilla.org/appshell/window-mediator;1"]
  100.           .getService(CI.nsIWindowMediator);
  101.  
  102. function loadSubScript(spec) {
  103.   var loader = CC[JS_SUBSCRIPT_LOADER_CONTRACTID]
  104.                .getService(CI.mozIJSSubScriptLoader);
  105.   var context = {};
  106.   loader.loadSubScript(spec, context);
  107.   return context;
  108. }
  109.  
  110. function loadLibraryFromSpec(aSpec) {
  111.   var loader = CC[JS_SUBSCRIPT_LOADER_CONTRACTID]
  112.                .getService(CI.mozIJSSubScriptLoader);
  113.   loader.loadSubScript(aSpec);
  114. }
  115.  
  116. loadLibraryFromSpec("chrome://browser/content/flock/photo/photoAPI.js");
  117. loadLibraryFromSpec("chrome://flock/content/common/flocksafe.js");
  118.  
  119. function _getIdentityUrn(aAccountId, aUid) {
  120.   var result = FACEBOOK_IDENTITY_URN_PREFIX
  121.              + aAccountId + ":"
  122.              + aUid;
  123.  
  124.   return result;
  125. }
  126.  
  127. // A special hack to trigger embedding rich content into a Facebook field.
  128. // Added as a general helper function as it is used in both FacebookService
  129. // and FacebookAccount classes.
  130. function _initKeyupEvent(aTarget) {
  131.   if (!aTarget) {
  132.     return;
  133.   }
  134.  
  135.   var doc = aTarget.ownerDocument;
  136.   if (!(doc instanceof CI.nsIDOMHTMLDocument)) {
  137.     return;
  138.   }
  139.  
  140.   var docView = doc.QueryInterface(CI.nsIDOMDocumentView).defaultView;
  141.   if (!(docView instanceof CI.nsIDOMAbstractView)) {
  142.     return;
  143.   }
  144.  
  145.   // add an extra space as the first of two parts to simulate the keyup event
  146.   aTarget.value += " ";
  147.  
  148.   // Send a key-up event to the field: this triggers a Facebook
  149.   // process which scrapes the field for a link which, if found,
  150.   // will embed the rich content into the field.
  151.   var eventObj = doc.createEvent("KeyEvents");
  152.   eventObj.initKeyEvent("keyup", true, true, docView, false, false, false,
  153.                         false, CI.nsIDOMKeyEvent.DOM_VK_SPACE, CHAR_CODE_SPACE);
  154.   aTarget.dispatchEvent(eventObj);
  155.  
  156.   // Remove the space to get back to the original value
  157.   var val = aTarget.value;
  158.   aTarget.value = val.substring(0, val.length - 1);
  159. }
  160.  
  161. // Appends the Flock breadcrumb to the given text field
  162. function _addBreadcrumb(aField) {
  163.   if (aField) {
  164.     var breadcrumb = CC[FLOCK_RDNDS_CONTRACTID]
  165.                      .getService(CI.flockIRichDNDService)
  166.                      .getBreadcrumb("plain");
  167.     if (breadcrumb) {
  168.       aField.value += breadcrumb;
  169.     }
  170.   }
  171. }
  172.  
  173.  
  174. //******************************************
  175. //* Service Class
  176. //******************************************
  177.  
  178. function flockFacebookService() {
  179.   this.obs = CC[OBSERVER_SERVICE_CONTRACTID].getService(CI.nsIObserverService);
  180.   this.acUtils = CC[FLOCK_ACCOUNT_UTILS_CONTRACTID].
  181.                  getService(CI.flockIAccountUtils);
  182.  
  183.   this.status = CI.flockIWebService.STATUS_UNKNOWN;
  184.   this.url = "http://www.facebook.com";
  185.   this.api = CC["@flock.com/api/facebook;1"].getService(CI.flockIFacebookAPI).wrappedJSObject;
  186.   this.mIsInitialized = false;
  187.   this.photoList = null;    
  188.  
  189.   this._ctk = {
  190.     interfaces: [
  191.       "nsISupports",
  192.       "nsIClassInfo",
  193.       "nsISupportsCString",
  194.       "nsIObserver",
  195.       "flockIWebService",
  196.       "flockIAuthenticateNewAccount",
  197.       "flockISocialWebService",
  198.       "flockIManageableWebService",
  199.       "flockIPollingService",
  200.       "flockIMediaWebService",
  201.       "flockIRichContentDropHandler"
  202.     ],
  203.     shortName: "facebook",
  204.     fullName: "Facebook",
  205.     description: FACEBOOK_TITLE,
  206.     domains: gStrings["domains"],
  207.     favicon: FACEBOOK_FAVICON,
  208.     CID: FACEBOOK_CID,
  209.     contractID: FACEBOOK_CONTRACTID,
  210.     accountClass: flockFacebookAccount,
  211.     needPassword: true
  212.   };
  213.  
  214.   var sbs = CC["@mozilla.org/intl/stringbundle;1"]
  215.             .getService(CI.nsIStringBundleService);
  216.   var bundle = sbs.createBundle(FACEBOOK_PROPERTIES);
  217.  
  218.   this._channels = {
  219.     "special:photosFromFriends": {
  220.       title: bundle.GetStringFromName("flock.facebook.title.friends"),
  221.       supportsSearch: false
  222.     },
  223.     "special:photosOfFriends": {
  224.       title: bundle.GetStringFromName("flock.facebook.title.recent"),
  225.       supportsSearch: false
  226.     },
  227.       "special:photosOfYou": {
  228.       title: bundle.GetStringFromName("flock.facebook.title.you"),
  229.       supportsSearch: false
  230.     }
  231.   };
  232.  
  233.   this._logger = CC["@flock.com/logger;1"].createInstance(CI.flockILogger);
  234.   this._logger.init("facebookService");
  235.   this._profiler = CC["@flock.com/profiler;1"].getService(CI.flockIProfiler);
  236.   this.init();
  237. }
  238.  
  239. flockFacebookService.prototype.init =
  240. function flockFacebookService_init() {
  241.   // Prevent re-entry
  242.   if (this.mIsInitialized) {
  243.     return;
  244.   }
  245.   this.mIsInitialized = true;
  246.   this._logger.info(".init()");
  247.  
  248.   // Determine whether this service has been disabled, and unregister if so.
  249.   this.prefService = CC["@mozilla.org/preferences-service;1"]
  250.                      .getService(CI.nsIPrefBranch);
  251.   if (this.prefService.getPrefType(SERVICE_ENABLED_PREF) &&
  252.        !this.prefService.getBoolPref(SERVICE_ENABLED_PREF)) {
  253.     this._logger.info(SERVICE_ENABLED_PREF + " is FALSE! Not initializing.");
  254.     var cm = CC[CATEGORY_MANAGER_CONTRACTID].getService(CI.nsICategoryManager);
  255.     cm.deleteCategoryEntry("wsm-startup", CATEGORY_COMPONENT_NAME, true);
  256.     cm.deleteCategoryEntry("flockWebService", CATEGORY_ENTRY_NAME, true);
  257.     cm.deleteCategoryEntry("flockMediaProvider", CATEGORY_ENTRY_NAME, true);
  258.     cm.deleteCategoryEntry(FLOCK_RICH_CONTENT_CATEGORY_ENTRY,
  259.                            CATEGORY_ENTRY_NAME, true);
  260.     return;
  261.   }
  262.  
  263.   this.faves_coop = CC[FLOCK_SINGLETON_CONTRACTID]
  264.                     .getService(CI.flockISingleton)
  265.                     .getSingleton(FAVES_COOP)
  266.                     .wrappedJSObject;
  267.  
  268.   this.account_root = this.faves_coop.accounts_root;
  269.  
  270.   this.fbService = new this.faves_coop.Service(
  271.     "urn:facebook:service", {
  272.       name: "facebook",
  273.       desc: "Facebook"
  274.     });
  275.   this.fbService.serviceId = FACEBOOK_CONTRACTID;
  276.  
  277.   this.urn = this.fbService.id();
  278.  
  279.   // Load Web Detective file
  280.   this.webDetective = this.acUtils.useWebDetective("facebook.xml");
  281.   for (var s in gStrings) {
  282.     gStrings[s] = this.webDetective.getString("facebook", s, gStrings[s]);
  283.   }
  284.   this.fbService.domains = gStrings["domains"];
  285.   this.fbService.loginURL = gStrings["userlogin"];
  286.  
  287.   // Update auth states
  288.   try {
  289.     if (this.webDetective.detectCookies("facebook", "loggedout", null)) {
  290.       this.acUtils.markAllAccountsAsLoggedOut(FACEBOOK_CONTRACTID);
  291.       this.logout();
  292.     }
  293.   } catch (ex) {
  294.     this._logger.error("ERROR updating auth states for Facebook: " + ex);
  295.   }
  296.  
  297.   // On browser restart if a Facebook coop account is still marked as
  298.   // authenticated we also need to reauth the api. We do this by calling
  299.   // login() on the xpcom account obj. However, we have to wait while
  300.   // the Facebook service is finished constructing itself.
  301.   var inst = this;
  302.   var reloginCallback = {
  303.     notify: function reloginCallback_notify(aTimer) {
  304.       inst._logger.debug("reloginCallback.notify()");
  305.       var acctURN = inst.acUtils
  306.                         .getFirstAuthenticatedAccountForService(FACEBOOK_CONTRACTID);
  307.  
  308.       if (acctURN) {
  309.         var account = inst.getAccount(acctURN);
  310.         if (account) {
  311.           var reloginListener = {
  312.             onSuccess: function reloginListener_onSuccess(aSubject, aTopic) {
  313.               inst._logger.debug(".init(): reloginListener: onSuccess()");
  314.             },
  315.             onError: function reloginListener_onError(aError) {
  316.               inst._logger.debug(".init(): reloginListener: onError()");
  317.               inst.faves_coop.get(acctURN).isAuthenticated = false;
  318.             }
  319.           };
  320.           account.login(reloginListener);
  321.         }
  322.       }
  323.     }
  324.   };
  325.   var reloginTimer = CC["@mozilla.org/timer;1"].createInstance(CI.nsITimer);
  326.   reloginTimer.initWithCallback(reloginCallback, 100, CI.nsITimer.TYPE_ONE_SHOT);
  327.  
  328. }
  329.  
  330. /**
  331.  * The following method defined in nsIObserver are provided by component
  332.  * toolkit:
  333.  *
  334.  * @see nsIObserver#observe
  335.  */
  336.  
  337. /**
  338.  * @see flockIPollingService#refresh
  339.  */
  340. flockFacebookService.prototype.refresh =
  341. function flockFacebookService_refresh(aUrn, aListener) {
  342.   this._logger.debug(".refresh() with aUrn of " + aUrn);
  343.   var refreshItem = this.faves_coop.get(aUrn);
  344.  
  345.   if (refreshItem instanceof this.faves_coop.Account) {
  346.     this._logger.debug("refreshing an Account");
  347.     if (this.api.isLoggedIn) {
  348.       this._refreshAccount(aUrn, aListener);
  349.     } else {
  350.       this._logger.debug("api is not logged in - skipping refresh");
  351.       // If the user is not logged in, return a success without
  352.       // refreshing anything
  353.       aListener.onResult();
  354.     }
  355.   } else if (refreshItem instanceof this.faves_coop.Identity) {
  356.     this._logger.debug("refreshing an Identity");
  357.     aListener.onResult();
  358.   } else {
  359.     throw CR.NS_ERROR_ABORT;
  360.     this._logger.error("can't refresh " + aUrn + " (unsupported type)");
  361.     aListener.onError(null);
  362.   }
  363. }
  364.  
  365. flockFacebookService.prototype._refreshAccount =
  366. function flockFacebookService_refreshAccount(aUrn, aListener) {
  367.   var inst = this;
  368.   var refreshItem = this.faves_coop.get(aUrn);
  369.   var lastUpdate = refreshItem.lastUpdateDate;
  370.   var peopleHash;
  371.   var numberOfAnswers = 0;
  372.  
  373.   function onIndividualSuccess() {
  374.     numberOfAnswers++;
  375.     if (numberOfAnswers >= 3) {
  376.       if (inst.acUtils.isPeopleSidebarOpen()) {
  377.         refreshItem.nextRefresh = new Date(Date.now()
  378.                                            + FACEBOOK_SHORT_INTERVAL * 1000);
  379.       }
  380.       if (aListener) {
  381.         aListener.onResult();
  382.       }
  383.     }
  384.   }
  385.  
  386.   var mediaFriendsListener = {
  387.     onSuccess: function mfl_onSuccess(aEnum, aStatus) {
  388.       inst._logger.info("Successful getUpdatedMediaFriends");
  389.       aEnum.QueryInterface(CI.nsISimpleEnumerator);
  390.  
  391.       // Get the media information, put it in the peopleHash
  392.       while (aEnum.hasMoreElements()) {
  393.         var bag = aEnum.getNext();
  394.         bag.QueryInterface(CI.nsIPropertyBag);
  395.         uid = bag.getProperty("uid");
  396.         peopleHash[uid].media = {
  397.           count: bag.getProperty("count"),
  398.           latest: bag.getProperty("latest")
  399.         }
  400.       }
  401.  
  402.       // Now that we have all we need, update the RDF
  403.       function myWorker(aShouldYield) {
  404.         var count = 0;
  405.         // ADD or update existing people
  406.         for (uid in peopleHash) {
  407.           count++;
  408.           inst._addPerson(peopleHash[uid], refreshItem);
  409.           if (aShouldYield()) {
  410.             yield;
  411.           }
  412.         }
  413.         // REMOVE locally people removed on the server
  414.         var localEnum = refreshItem.friendsList.children.enumerate();
  415.         while (localEnum.hasMoreElements()) {
  416.           var identity = localEnum.getNext();
  417.           if (!peopleHash[identity.accountId]) {
  418.             inst._logger.info("Friend " + identity.accountId
  419.                               + " has been deleted on the server");
  420.             refreshItem.friendsList.children.remove(identity);
  421.             identity.destroy();
  422.           }
  423.         }
  424.         onIndividualSuccess();
  425.       }
  426.       getCompTK().schedule(gTimers, 0.05, 10, myWorker);
  427.     },
  428.     onError: function mfl_onError(aError) {
  429.       inst._logger.error("Error on getUpdatedMediaFriends");
  430.     }
  431.   }
  432.  
  433.   var friendsGetListener = {
  434.     onSuccess: function fgl_onSuccess(aResult, aStatus) {
  435.       inst._logger.info("Success for friendsGetListener");
  436.  
  437.       peopleHash = aResult.wrappedJS;
  438.  
  439.       refreshItem.lastUpdateDate = new Date();
  440.       var lastUpdateStr;
  441.       if (lastUpdate) {
  442.         // Convert milliseconds to seconds then to a string
  443.         lastUpdateStr = new String(parseInt(lastUpdate.getTime() / 1000));
  444.       } else {
  445.         lastUpdateStr = new String(parseInt(Date.now() / 1000)
  446.                                    - MEDIA_INITIAL_FETCH);
  447.       }
  448.       inst.api.getUpdatedMediaFriends(mediaFriendsListener, lastUpdateStr);
  449.     },
  450.     onError: function fgl_onError() {
  451.       inst._logger.error("Error on gettingFriends")
  452.     }
  453.   };
  454.  
  455.   var usersGetInfoListener = {
  456.     onSuccess: function ugil_onSuccess(aEnum, aStatus) {
  457.       inst._logger.info("Success for usersGetInfo");
  458.       aEnum.QueryInterface(CI.nsISimpleEnumerator);
  459.       if (aEnum.hasMoreElements()) {
  460.         var bag = aEnum.getNext();
  461.         bag.QueryInterface(CI.nsIPropertyBag);
  462.         refreshItem.avatar = bag.getProperty("avatar");
  463.         // We get the full name each refresh since it's possible for the
  464.         // user's full name to change.
  465.         refreshItem.name = bag.getProperty("name");
  466.         refreshItem.statusMessage = bag.getProperty("statusMessage");
  467.         refreshItem.lastProfileUpdate = bag.getProperty("lastProfileUpdate");
  468.  
  469.         onIndividualSuccess();
  470.       }
  471.     },
  472.     onError: function ugil_onError() {
  473.       inst._logger.error("Error on usersGetInfo");
  474.     }
  475.   };
  476.  
  477.   var notificationsGetListener = {
  478.     onSuccess: function ngl_onSuccess(aEnum, aStatus) {
  479.       inst._logger.info("Success for notificatonsGet");
  480.       aEnum.QueryInterface(CI.nsISimpleEnumerator);
  481.       if (aEnum.hasMoreElements()) {
  482.         var bag = aEnum.getNext();
  483.         bag.QueryInterface(CI.nsIPropertyBag);
  484.  
  485.         // if any of the Me notifications have increased in count,
  486.         // light the people button
  487.         if ((refreshItem.accountMessages < bag.getProperty("messages")) ||
  488.             (refreshItem.fbPokes < bag.getProperty("pokes")) ||
  489.             (refreshItem.fbFriendRequests < bag.getProperty("friendRequests")) ||
  490.             (refreshItem.fbGroupInvites < bag.getProperty("groupInvites")) ||
  491.             (refreshItem.fbEventInvites < bag.getProperty("eventInvites")))
  492.         {
  493.           inst._lightPeopleIcon();
  494.         }
  495.  
  496.         // write notifications to RDF
  497.         refreshItem.accountMessages = bag.getProperty("messages");
  498.         refreshItem.fbPokes = bag.getProperty("pokes");
  499.         refreshItem.fbFriendRequests = bag.getProperty("friendRequests");
  500.         refreshItem.fbGroupInvites = bag.getProperty("groupInvites");
  501.         refreshItem.fbEventInvites = bag.getProperty("eventInvites");
  502.       }
  503.  
  504.       onIndividualSuccess();
  505.     },
  506.     onError: function ngl_onError() {
  507.       inst._logger.error("Error on notificatonsGet");
  508.     }
  509.   };
  510.  
  511.   this.api.usersGetInfo(usersGetInfoListener,
  512.                         this.api.uid,
  513.                         ["name,pic_square,status"]);
  514.   this.api.friendsGet(friendsGetListener);
  515.   this.api.notificationsGet(notificationsGetListener);
  516. }
  517.  
  518. flockFacebookService.prototype._incrementMedia =
  519. function flockFacebookService_incrementMedia(aUid, aCount, aLatest) {
  520.   var currAcctURN = this.acUtils.getFirstAuthenticatedAccountForService(FACEBOOK_CONTRACTID);
  521.   var currAcct = this.faves_coop.get(currAcctURN);
  522.  
  523.   // Update the count on the identity...
  524.   var identityUrn = _getIdentityUrn(currAcct.accountId, aUid);
  525.   identity = this.faves_coop.get(identityUrn);
  526.   identity.unseenMedia += aCount;
  527.  
  528.   // + 10 explaination: when the friend uploads a new photo, Facebook
  529.   // updates the profile update time a few seconds after the photo upload.
  530.   // We want that to show as a "media" update, not as a "profile" update
  531.   if ((aLatest + 10) >= identity.lastUpdate) {
  532.     identity.lastUpdate = aLatest;
  533.     identity.lastUpdateType = "media";
  534.   }
  535. }
  536.  
  537.  
  538. flockFacebookService.prototype._addPerson =
  539. function flockFacebookService_addPerson(aPerson, aCoopAccount) {
  540.   var person = aPerson;
  541.  
  542.   // We include the current accountId in the identity urn to prevent friend
  543.   // collisions if multiple Facebook accounts have the same friend.
  544.   var identityUrn = _getIdentityUrn(aCoopAccount.accountId,
  545.                                     person.uid);
  546.   var updating = this.faves_coop.Identity.exists(identityUrn);
  547.   var identity;
  548.  
  549.   var lastProfileUpdate = parseInt(person.profile_update_time, 10);
  550.   var statusTime = parseInt(person.status.time, 10);
  551.   var lastUpdate = 0;
  552.   var lastUpdateType;
  553.   if (statusTime > lastProfileUpdate) {
  554.     lastUpdate = statusTime;
  555.     lastUpdateType = "status";
  556.   } else {
  557.     lastUpdate = lastProfileUpdate;
  558.     lastUpdateType = "profile";
  559.   }
  560.   this._logger.debug("Adding person: " + person.uid + " - " + person.name);
  561.  
  562.   if (updating) {
  563.     // update data of the identity coop obj here
  564.     identity = this.faves_coop.get(identityUrn);
  565.     if (lastUpdate > identity.lastUpdate) {
  566.       identity.lastUpdate = lastUpdate;
  567.       identity.lastUpdateType = lastUpdateType;
  568.       identity.name = person.name;
  569.       identity.avatar = person.pic_square;
  570.       identity.statusMessage = person.status.message;
  571.     }
  572.   } else {
  573.     identity = new this.faves_coop.Identity(
  574.       identityUrn,
  575.       {
  576.         name: person.name,
  577.         serviceId: FACEBOOK_CONTRACTID,
  578.         accountId: person.uid,
  579.         avatar: person.pic_square,
  580.         statusMessage: person.status.message,
  581.         lastUpdate: lastUpdate,
  582.         lastUpdateType: lastUpdateType
  583.       }
  584.     );
  585.     aCoopAccount.friendsList.children.add(identity);
  586.   }
  587.  
  588.   if (person.media) {
  589.     this._incrementMedia(person.uid,
  590.                          person.media.count,
  591.                          person.media.latest);
  592.   }
  593. }
  594.  
  595. flockFacebookService.prototype.markAllMediaSeen =
  596. function flockFacebookService_markAllMediaSeen(aIdentityUrn) {
  597.   var identity = this.faves_coop.get(aIdentityUrn);
  598.   identity.unseenMedia = 0;
  599. }
  600.  
  601. // Facebook's HTML sets a maxlength of 160 on the status entry textbox.
  602. flockFacebookService.prototype.maxStatusLength = 160;
  603.  
  604. flockFacebookService.prototype._lightPeopleIcon =
  605. function flockFacebookService_lightPeopleIcon() {
  606.   this._logger.debug("._lightPeopleIcon()");
  607.   this.obs.notifyObservers(null, "new-people-notification", null);
  608. }
  609.  
  610. flockFacebookService.prototype._addPhotostream =
  611. function flockFacebookService_addPhotostream(aPhotoPerson, aCoopAccount) {
  612.   var mediaFavesUrn = "urn:media:favorites";
  613.   var query = new queryHelper();
  614.   var person = aPhotoPerson.QueryInterface(CI.nsIPropertyBag);
  615.   query.user = person.getProperty("uid");
  616.   query.username = person.getProperty("name");
  617.   this._logger.info(".addPhotostream('" + query.username + "')");
  618.   var mediaQueryUrn = mediaFavesUrn + ":facebook:" + query.stringVal;
  619.   var mediaQuery = this.faves_coop.get(mediaQueryUrn);
  620.   if (!mediaQuery) {
  621.     mediaQuery = new this.faves_coop.MediaQuery(
  622.       mediaQueryUrn,
  623.       {
  624.         serviceId: FLOCK_PHOTO_API_MANAGER_CONTRACTID,
  625.         service: this.shortName,
  626.         favicon: FACEBOOK_FAVICON,
  627.         isPollable: true,
  628.         query: query.stringVal,
  629.         name: person.getProperty("name")
  630.       }
  631.     );
  632.   }
  633.   mediaQuery.isTransient = aCoopAccount.isTransient;
  634.   var mediaFaves = this.faves_coop.get(mediaFavesUrn);
  635.   if (!mediaFaves) {
  636.     mediaFaves = new this.faves_coop.Folder(mediaFavesUrn);
  637.     this.faves_coop.favorites_root.children.add(mediaFaves);
  638.   }
  639.   mediaFaves.children.addOnce(mediaQuery);
  640. }
  641.  
  642. /**
  643.  * The following attributes and methods defined in flockIWebService are
  644.  * provided by component toolkit:
  645.  *
  646.  * @see flockIWebService#icon
  647.  * @see flockIWebService#needPassword
  648.  * @see flockIWebService#shortName
  649.  * @see flockIWebService#title
  650.  * @see flockIWebService#urn
  651.  * @see flockIWebService#getAccount
  652.  * @see flockIWebService#getAccounts
  653.  * @see flockIWebService#removeAccount
  654.  */
  655.  
  656. /**
  657.  * @see flockIWebService#addAccountById
  658.  */
  659. flockFacebookService.prototype.addAccountById =
  660. function flockFacebookService_addAccountById(aAccountID,
  661.                                              aIsTransient,
  662.                                              aListener) {
  663.   this._logger.debug(".addAccountById('"
  664.                      + aAccountID + "', "
  665.                      + aIsTransient + "...)");
  666.  
  667.   var pw = this.acUtils.getPassword(this.urn + ":" + aAccountID);
  668.   /// @todo: FIX ME - name shouldn't be email address (pw.user) - DP
  669.   var name = (pw) ? pw.user : aAccountID;
  670.  
  671.   var accountUrn = "urn:flock:facebook:account:" + aAccountID;
  672.   var account = new this.faves_coop.Account(
  673.     accountUrn, {
  674.       name: name,
  675.       serviceId: FACEBOOK_CONTRACTID,
  676.       service: this.fbService,
  677.       accountId: aAccountID,
  678.       isPollable: false,
  679.       refreshInterval: FACEBOOK_REFRESH_INTERVAL,
  680.       URL: gStrings["userprofile"].replace("%accountid%", aAccountID),
  681.       isTransient: aIsTransient,
  682.       favicon: FACEBOOK_FAVICON
  683.     });
  684.  
  685.   this.account_root.children.add(account);
  686.   this.USER = account.id();
  687.  
  688.   var friendsListUrn = accountUrn + ":friends";
  689.   var friendsList = new this.faves_coop.FriendsList(
  690.     friendsListUrn,
  691.     {
  692.       account: account
  693.     });
  694.   account.friendsList = friendsList;
  695.  
  696.   // Instantiate account component
  697.   var acct = this.getAccount(account.id());
  698.   if (aListener) {
  699.     aListener.onSuccess(acct, "addAccount");
  700.   }
  701.   return acct;
  702. }
  703.  
  704. // BEGIN flockIAuthenticateNewAccount interface
  705. flockFacebookService.prototype.authenticateNewAccount =
  706. function flockFacebookService_authenticateNewAccount(aListener)
  707. {
  708.   this._logger.info("{flockIAuthenticateNewAccount}.authenticateNewAccount(aListener)");
  709.   var wm = CC["@mozilla.org/appshell/window-mediator;1"]
  710.            .getService(CI.nsIWindowMediator);
  711.   var win = wm.getMostRecentWindow('navigator:browser');
  712.   win.openUILinkIn(this.url, "tab");
  713. }
  714. // END flockIAuthenticateNewAccount interface
  715.  
  716. /**
  717.  * The following methods defined in flockIManageableWebService are provided by
  718.  * component toolkit:
  719.  *
  720.  * @see flockIManageableWebService#docRepresentsSuccessfulLogin
  721.  * @see flockIManageableWebService#getAccountIDFromDocument
  722.  * @see flockIManageableWebService#getCredentialsFromForm
  723.  * @see flockIManageableWebService#ownsDocument
  724.  * @see flockIManageableWebService#ownsLoginForm
  725.  */
  726.  
  727. /**
  728.  * @see flockIManageableWebService#updateAccountStatusFromDocument
  729.  */
  730. flockFacebookService.prototype.updateAccountStatusFromDocument =
  731. function flockFacebookService_updateAccountStatusFromDocument(aDocument) {
  732.   this._logger.debug(".updateAccountStatusFromDocument(aDocument)");
  733.   if (this.webDetective.detect("facebook", "loggedout", aDocument, null)) {
  734.     this.acUtils.markAllAccountsAsLoggedOut(FACEBOOK_CONTRACTID);
  735.     this.api.deauthenticate();
  736.   } else if (this.webDetective.detect("facebook",
  737.                                       "loggedin",
  738.                                       aDocument,
  739.                                       null))
  740.   {
  741.     var results = CC[HASH_PROPERTY_BAG_CONTRACTID]
  742.                   .createInstance(CI.nsIWritablePropertyBag2);
  743.     if (this.webDetective.detect("facebook",
  744.                                  "accountinfo",
  745.                                  aDocument,
  746.                                  results))
  747.     {
  748.       var acctId = results.getPropertyAsAString("accountid");
  749.       var acctUrn = this.acUtils.getAccountURNById(this.urn, acctId);
  750.       var acct = this.faves_coop.get(acctUrn);
  751.       if (!acct.isAuthenticated) {
  752.         var inst = this;
  753.         var loginListener = {
  754.           onSuccess: function loginListener_onSuccess() {
  755.             inst._logger.info(".updateAccountStatusFromDocument(): "
  756.                               + "loginListener: onSuccess()");
  757.             acct.isAuthenticated = true;
  758.           },
  759.           onError: function loginListener_onError(aError) {
  760.             inst._logger.info(".updateAccountStatusFromDocument(): "
  761.                               + "loginListener: onError()");
  762.             inst._logger.info(aError ? (aError.errorString) : "No details");
  763.             acct.isAuthenticated = false;
  764.           }
  765.         };
  766.         this.getAccount(acctUrn).login(loginListener);
  767.       }
  768.     }
  769.   }
  770. }
  771.  
  772.  
  773.  
  774.  
  775. /**
  776.  * createAlbum
  777.  * @see flockIMediaWebService#createAlbum
  778.  */
  779. flockFacebookService.prototype.createAlbum =
  780. function flockFacebookService_createAlbum(aListener, aTitle) {
  781.   // trim white space from front and end of string
  782.   var newTitle = aTitle.replace(/^\s+|\s+$/g, "");
  783.   if (newTitle) {
  784.     var myListener = {
  785.       onSuccess: function listener_onSuccess(aAlbum, aStatus) {
  786.         aListener.onCreateAlbum(aAlbum);
  787.       },
  788.       onError: function listener_onError(aFlockError) {
  789.         aListener.onError(aFlockError);
  790.       }
  791.     }
  792.     this.api.createAlbum(myListener, newTitle);
  793.   } else {
  794.     var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
  795.     error.errorCode = CI.flockIError.PHOTOSERVICE_EMPTY_ALBUMNAME;
  796.     aListener.onError(null, null, error);
  797.   }
  798. }
  799.  
  800.  
  801. /**
  802.  * findByUsername
  803.  * @see flockIMediaWebService#findByUsername
  804.  */
  805. flockFacebookService.prototype.findByUsername =
  806. function flockFacebookService_findByUsername(aListener, aUsername) {
  807.   // need to call this to trigger mediaObserver
  808.   aListener.onError(null);
  809. }
  810.  
  811. /**
  812.  * getAccountStatus
  813.  *
  814.  * This currently returns hardcoded values so the uploader works.  This
  815.  * should be replaced by actual calls to the Facebok API, or with values
  816.  * screen-scraped by Web Detective.
  817.  *
  818.  * @see flockIMediaWebService#getAccountStatus
  819.  * @todo Use the Facebook API or Web Detective to correctly obtain this
  820.  *       information.
  821.  * @todo Localize the hardcoded strings.
  822.  */
  823. flockFacebookService.prototype.getAccountStatus =
  824. function flockFacebookService_getAccountStatus(aListener) {
  825.   var result = CC["@mozilla.org/hash-property-bag;1"]
  826.                .createInstance(CI.nsIWritablePropertyBag2);
  827.  
  828.   result.setPropertyAsAString("maxSpace", FACEBOOK_MAX_PHOTO_SPACE);
  829.   result.setPropertyAsAString("usedSpace", FACEBOOK_USED_PHOTO_SPACE);
  830.   result.setPropertyAsAString("maxFileSize", FACEBOOK_MAX_FILE_SIZE);
  831.   // This is a key, not something displayed to the user. Don't l10n this!
  832.   result.setPropertyAsAString("usageUnits", "bytes");
  833.   result.setPropertyAsBool("isPremium", true); // Facebook has no "premium" level.
  834.  
  835.   aListener.onSuccess(result, "");
  836. }
  837.  
  838. /**
  839.  * getAlbums
  840.  * @see flockIMediaWebService#getAlbums
  841.  *
  842.  * aUsername is the FB uid
  843.  */
  844. flockFacebookService.prototype.getAlbums =
  845. function flockFacebookService_getAlbums(aListener, aUsername) {
  846.   this._logger.info(".getAlbums(aListener, " + aUsername + ")");
  847.   var myListener = {
  848.     onSuccess: function listener_onSuccess(aEnumResults, aStatus) {
  849.       aListener.onGetAlbumsResult(aEnumResults);
  850.     },
  851.     onError: function listener_onError(aSubject, aTopic, aFlockError) {
  852.       aListener.onError(aFlockError);
  853.     }
  854.   };
  855.  
  856.   this.api.getAlbums(myListener, aUsername);
  857. }
  858.  
  859. /**
  860.  * getAuthPerson
  861.  * @see flockIMediaWebService#getAuthPerson
  862.  */
  863. flockFacebookService.prototype.getAuthPerson =
  864. function flockFacebookService_getAuthPerson () {
  865.   try {
  866.     var person = {};
  867.     person.id = this.api.uid;
  868.     // XXX Localize these
  869.     person.fullname = "Unknown";
  870.     person.username = "Unknown";
  871.     person.service = this;
  872.     return person;
  873.   } catch (ex) {
  874.     return null;
  875.   }
  876. }
  877.  
  878.  
  879. /**
  880.  * getContacts
  881.  * @see flockIMediaWebService#getContacts
  882.  * @todo Implement this, or handle the error more gracefully.
  883.  * @throws NS_ERROR_NOT_IMPLEMENTED
  884.  */
  885. flockFacebookService.prototype.getContacts =
  886. function flockFacebookService_getContacts(aListener) {
  887.   throw CR.NS_ERROR_NOT_IMPLEMENTED;
  888. }
  889.  
  890. /**
  891.  * getMostRecentPhotoForList
  892.  * @see flockIMediaWebService#getMostRecentPhotoForList
  893.  * @todo Implement this, or handle the error more gracefully.
  894.  * @throws NS_ERROR_NOT_IMPLEMENTED
  895.  */
  896. flockFacebookService.prototype.getMostRecentPhotoForList =
  897. function flockFacebookService_getMostRecentPhotoForList(aListener,
  898.                                                         aEnumerator) {
  899.   throw CR.NS_ERROR_NOT_IMPLEMENTED;
  900. }
  901.  
  902. /**
  903.  * getPhoto
  904.  * @see flockIMediaWebService#getPhoto
  905.  * @todo Implement this, or handle the error more gracefully.
  906.  * @throws NS_ERROR_NOT_IMPLEMENTED
  907.  */
  908. flockFacebookService.prototype.getPhoto =
  909. function flockFacebookService_getPhoto(aListener, aPhotoId) {
  910.   throw CR.NS_ERROR_NOT_IMPLEMENTED;
  911. }
  912.  
  913. /**
  914.  * getPhotoFromRDFNode
  915.  * @see flockIMediaWebService#getPhotoFromRDFNode
  916.  */
  917. flockFacebookService.prototype.getPhotoFromRDFNode =
  918. function flockFacebookService_getPhotoFromRDFNode(aRdfId) {
  919.   var newPhoto = this.api.createPhoto();
  920.   var coopPhoto = this.faves_coop.get(aRdfId);
  921.   newPhoto.webPageUrl = coopPhoto.URL;
  922.   newPhoto.thumbnail = coopPhoto.thumbnail;
  923.   newPhoto.midSizePhoto = coopPhoto.midSizePhoto;
  924.   newPhoto.largeSizePhoto = coopPhoto.largeSizePhoto;
  925.   newPhoto.username = coopPhoto.username;
  926.   newPhoto.userid = coopPhoto.userid;
  927.   newPhoto.title = coopPhoto.name;
  928.   newPhoto.id = coopPhoto.photoid;
  929.   newPhoto.icon = coopPhoto.favicon;
  930.   newPhoto.uploadDate = coopPhoto.datevalue;
  931.   newPhoto.is_public = coopPhoto.is_public;
  932.   newPhoto.is_video = coopPhoto.is_video;
  933.  
  934.   return newPhoto;
  935. }
  936.  
  937. /**
  938.  * logout
  939.  * @see flockIWebService#logout
  940.  */
  941. flockFacebookService.prototype.logout =
  942. function flockFacebookService_logout() {
  943.   this._logger.debug(".logout()");
  944.   this.api.deauthenticate();
  945.   this.acUtils.removeCookies(this.webDetective.getSessionCookies("facebook"));
  946.   this.USER = null;
  947. }
  948.  
  949. /**
  950.  * search
  951.  * @see flockIMediaWebService#search
  952.  */
  953. flockFacebookService.prototype.search =
  954. function flockFacebookService_search(aListener, aQueryString, aCount, aPage) {
  955.   this._logger.debug("Attempting to run search query: " + aQueryString);
  956.   var inst = this;
  957.   if (aPage > 1) {
  958.     // Implement a pseudo-pagination since Facebook does not support pagination
  959.     getPhotosDetails(aCount, aPage);
  960.     return;
  961.   }
  962.  
  963.   var aQuery = new queryHelper(aQueryString);
  964.  
  965.   function createEnum(myarray) {
  966.     myarray.reverse();
  967.     return {
  968.       QueryInterface: function (iid) {
  969.         if (iid.equals(Components.interfaces.nsISimpleEnumerator)) {
  970.           return this;
  971.         }
  972.         throw Components.results.NS_ERROR_NO_INTERFACE;
  973.       },
  974.       hasMoreElements: function() {
  975.         return (myarray.length>0);
  976.       },
  977.       getNext: function() {
  978.         return myarray.shift();
  979.       }
  980.     }
  981.   }
  982.  
  983.   function getPhotosDetails(aCount, aPage) {
  984.     var photos = [];
  985.     if (inst.photoList) {
  986.       var owners = [];
  987.  
  988.       var sbs = CC["@mozilla.org/intl/stringbundle;1"].
  989.                 getService(CI.nsIStringBundleService);
  990.       var bundleUrl = "chrome://flock/locale/photo/mediabar.properties";
  991.       var bundle = sbs.createBundle(bundleUrl);
  992.  
  993.       var pageEnd = aCount * aPage;
  994.       if (pageEnd > inst.photoList.length) {
  995.         pageEnd = inst.photoList.length;
  996.       }
  997.       for (var i = aCount * (aPage - 1); i < pageEnd; i++) {
  998.         var photo = inst.photoList[i];
  999.         var id = photo.getElementsByTagName('pid')[0]
  1000.                       .firstChild
  1001.                       .nodeValue;
  1002.         var title;
  1003.         if (photo.getElementsByTagName('caption')[0].firstChild) {
  1004.           title = photo.getElementsByTagName('caption')[0]
  1005.                        .firstChild
  1006.                        .nodeValue;
  1007.         } else {
  1008.           title = bundle.GetStringFromName("flock.media.tooltip.untitled");
  1009.         }
  1010.         var owner = photo.getElementsByTagName('owner')[0]
  1011.                          .firstChild.nodeValue;
  1012.         var date_upload = photo.getElementsByTagName('created')[0]
  1013.                                .firstChild
  1014.                                .nodeValue;
  1015.         var square_url = photo.getElementsByTagName('src')[0]
  1016.                               .firstChild
  1017.                               .nodeValue;
  1018.         var small_url = photo.getElementsByTagName('src_small')[0]
  1019.                              .firstChild
  1020.                              .nodeValue;
  1021.         var big_url = photo.getElementsByTagName('src_big')[0]
  1022.                            .firstChild
  1023.                            .nodeValue;
  1024.         var page_url = photo.getElementsByTagName('link')[0]
  1025.                             .firstChild
  1026.                             .nodeValue;
  1027.         var newPhoto = inst.api.createPhoto();
  1028.         newPhoto.webPageUrl = page_url;
  1029.         newPhoto.thumbnail = small_url;
  1030.         newPhoto.midSizePhoto = square_url;
  1031.         newPhoto.largeSizePhoto = big_url;
  1032.         newPhoto.userid = owner;
  1033.         newPhoto.title = title;
  1034.         newPhoto.id = id;
  1035.         newPhoto.uploadDate = parseInt(date_upload) * 1000;
  1036.         photos.push(newPhoto);
  1037.         owners.push(owner);
  1038.       }
  1039.       if (pageEnd == inst.photoList.length) {
  1040.         inst.photoList = null;
  1041.       }
  1042.     }
  1043.  
  1044.     // Exit this whole thing early if we got nothing back.
  1045.     if (photos.length == 0) {
  1046.       aListener.onSearchResult(createEnum(photos));
  1047.       return;
  1048.     }
  1049.  
  1050.     // We sort the array to ease de-dupeing by placing duplicate numbers
  1051.     // next to each other.
  1052.     owners = owners.sort();
  1053.     var ownerList = "";
  1054.     var prev = 0;
  1055.     for each (var owner in owners) {
  1056.       if (owner == prev) {
  1057.         continue;
  1058.       }
  1059.       if (ownerList != "") {
  1060.         ownerList += ",";
  1061.       }
  1062.       ownerList += owner;
  1063.       prev = owner;
  1064.     }
  1065.  
  1066.     if (ownerList != "") {
  1067.       var detailsListener = {
  1068.         onSuccess: function listener_onSuccess(aEnum) {
  1069.           var ownersObject = {};
  1070.           aEnum = aEnum.QueryInterface(CI.nsISimpleEnumerator);
  1071.           while (aEnum.hasMoreElements()) {
  1072.             var person = aEnum.getNext();
  1073.             person = person.QueryInterface(CI.nsIWritablePropertyBag2);
  1074.             ownersObject[person.getProperty("uid")] = person;
  1075.           }
  1076.  
  1077.           for (var j = 0; j < photos.length; j++) {
  1078.             var owner = ownersObject[photos[j].userid];
  1079.             owner = owner.QueryInterface(CI.nsIWritablePropertyBag2);
  1080.             photos[j].username = owner.getProperty("name");
  1081.             var avatar = owner.getProperty("avatar");
  1082.             if (avatar == "") {
  1083.               avatar = FLOCK_SNOWMAN_URL;
  1084.             }
  1085.             photos[j].icon = avatar;
  1086.           }
  1087.           photos.sort(function (a,b) {
  1088.             return a.uploadDate - b.uploadDate;
  1089.           });
  1090.           aListener.onSearchResult(createEnum(photos));
  1091.         }
  1092.       };
  1093.       inst.api.usersGetInfo(detailsListener,
  1094.                             ownerList,
  1095.                             ["name,pic_square"]);
  1096.     }
  1097.   }
  1098.  
  1099.   var fqlListener = {
  1100.     onSuccess: function listener_onSuccess(aXml, aStatus) {
  1101.       inst.photoList = aXml.getElementsByTagName("photo");
  1102.       getPhotosDetails(aCount, 1);
  1103.     },
  1104.     onError: function listener_onError(aSubject, aTopic, aFlockError) {
  1105.       aListener.onError(aFlockError);
  1106.     }
  1107.   };
  1108.  
  1109.   var qry = "SELECT pid, owner, src_small, src_big, src, link, caption, "
  1110.           + "created FROM photo ";
  1111.   if (aQuery.user) {
  1112.     // Query to get all photos by person with name defined in aQuery.search.
  1113.     // Could be more than one person's sets of photos.
  1114.     if (aQuery.album) {
  1115.       qry += "WHERE aid IN (" + aQuery.album + ")";
  1116.     } else {
  1117.       qry += "WHERE aid IN ("
  1118.            + "SELECT aid FROM album WHERE owner = '"
  1119.            + aQuery.user
  1120.            + "')";
  1121.     }
  1122.     this._logger.debug("Performing FQLQuery: " + qry);
  1123.     this.api.doFQLQuery(fqlListener, qry);
  1124.  
  1125.     return;
  1126.   } else if (aQuery.searchunique) {
  1127.     // Query to get all photos tagged with uid specified in aQuery.searchunique
  1128.     qry += "WHERE pid IN ("
  1129.          + "SELECT pid FROM photo_tag WHERE subject = '"
  1130.          + aQuery.searchunique
  1131.          + "')";
  1132.     this._logger.debug("Performing FQLQuery: " + qry);
  1133.     this.api.doFQLQuery(fqlListener, qry);
  1134.  
  1135.     return;
  1136.   } else if (aQuery.special) {
  1137.     switch (aQuery.special) {
  1138.       case "photosFromFriends":
  1139.         qry += "WHERE aid IN ("
  1140.              + "SELECT aid FROM album WHERE owner IN ("
  1141.              + "SELECT uid2 FROM friend WHERE uid1 = "
  1142.              + this.api.uid
  1143.              + ")) "
  1144.              + "AND created > now() - 2592000"; // One month (in seconds)
  1145.         break;
  1146.       case "photosOfFriends":
  1147.         qry += "WHERE pid IN ("
  1148.              + "SELECT pid FROM photo_tag WHERE subject IN ("
  1149.              + "SELECT uid2 FROM friend WHERE uid1 = "
  1150.              + this.api.uid
  1151.              + ")) "
  1152.              + "AND created > now() - 2592000"; // One month (in seconds)
  1153.         break;
  1154.       case "photosOfYou":
  1155.         qry += "WHERE pid IN ("
  1156.              + "SELECT pid FROM photo_tag WHERE subject="
  1157.              + this.api.uid
  1158.              + ")";
  1159.         break;
  1160.     }
  1161.     this._logger.debug("Performing FQLQuery: " + qry);
  1162.     this.api.doFQLQuery(fqlListener, qry);
  1163.  
  1164.     return;
  1165.   } else {
  1166.     // if user enters a search with illegal characters
  1167.     // (any non alpha-num or space)
  1168.     // then return right away with no results
  1169.     if (aQuery.search.match(/[^a-zA-Z0-9 ]/)) {
  1170.       aListener.onSearchResult(createEnum([]));
  1171.       return;
  1172.     }
  1173.   }
  1174.  
  1175.   function _normalizeQuery(input) {
  1176.     var output;
  1177.     var in_quotes = false;
  1178.  
  1179.     // strip leading spaces
  1180.     while (input.length && input[0] == " ") {
  1181.       // chop off the first character
  1182.       input = input.substr(1);
  1183.     }
  1184.  
  1185.     // Convert to lowercase for the wildcard search
  1186.     input = input.toLowerCase();
  1187.  
  1188.     // add leading plus
  1189.     output = "+";
  1190.     while (input.length) {
  1191.       if (input[0] == "\"") {
  1192.         if (in_quotes) {
  1193.           in_quotes = false;
  1194.         } else {
  1195.           in_quotes = true;
  1196.         }
  1197.       }
  1198.  
  1199.       // escape special chars
  1200.       if ("&|!(){}[]^~*?:\\".indexOf(input[0]) >= 0) {
  1201.         output = output + "\\";
  1202.       }
  1203.  
  1204.       output = output + input[0];
  1205.  
  1206.       if (!in_quotes && input[0] == " " && input.length > 1) {
  1207.           // put a plus before every word
  1208.           output = output + "+";
  1209.       }
  1210.  
  1211.       input = input.substr(1);
  1212.     }
  1213.  
  1214.     // remove trailing spaces
  1215.     var removed_spaces = false;
  1216.     while (output.length > 0 && output[output.length - 1] == " ") {
  1217.       output = output.substr(0, output.length - 1);
  1218.       removed_spaces = true;
  1219.     }
  1220.  
  1221.     // if we're in a quoted string we should close that
  1222.     if (in_quotes) {
  1223.       output = output + "\"";
  1224.     }
  1225.  
  1226.     // if theres an incomplete word
  1227.     if ((!removed_spaces) && (output[output.length - 1] != "\"")) {
  1228.       // search for partial matches
  1229.       output = output + "*";
  1230.     }
  1231.  
  1232.     return output;
  1233.   }
  1234.  
  1235.   // Query to get all photos tagged with name defined in aQuery.search.
  1236.   // We use Lucene to get partial matches.
  1237.   var searchService = CC["@flock.com/lucene/flockLucene;1"]
  1238.                       .getService(CI.flockILucene);
  1239.   var inst = this;
  1240.  
  1241.   var searchListener = {
  1242.     onSearchComplete:
  1243.     function searchListener_onSearchComplete(aNumResults, aResults) {
  1244.       inst._logger.debug("onSearchComplete: " + aNumResults);
  1245.       var uidArray = [];
  1246.       while (aResults.hasMoreElements()) {
  1247.         var result = aResults.getNext().QueryInterface(CI.flockILuceneResult);
  1248.         var uid = result.URI.split(":").pop();
  1249.         uidArray.push(uid);
  1250.       }
  1251.  
  1252.       var uidArrayStr = "(" + uidArray.join(",") + ")";
  1253.       var fullnameQuery = "(SELECT uid "
  1254.                         + "FROM user "
  1255.                         + "WHERE name = '"
  1256.                         + aQuery.search
  1257.                         + "')"
  1258.       qry += "WHERE pid IN ("
  1259.              + "SELECT pid FROM photo_tag "
  1260.              + "WHERE subject IN " + fullnameQuery + " "
  1261.              + "OR subject IN " + uidArrayStr
  1262.            + ")";
  1263.  
  1264.       inst._logger.debug("Performing FQLQuery: " + qry);
  1265.       inst.api.doFQLQuery(fqlListener, qry);
  1266.     }
  1267.   }
  1268.  
  1269.   searchService.search(_normalizeQuery(aQuery.search),
  1270.                        "identity",
  1271.                        100, searchListener);
  1272. }
  1273.  
  1274. /**
  1275.  * supportsFeature
  1276.  * @see flockIMediaWebService#supportsFeature
  1277.  */
  1278. flockFacebookService.prototype.supportsFeature =
  1279. function flockFacebookService_supportsFeature(aFeature) {
  1280.   var supports = {
  1281.     albumCreation: true,
  1282.     contacts: true,
  1283.     filename: false,
  1284.     privacy: false,
  1285.     tags: false,
  1286.     notes: true,
  1287.     title: false
  1288.   };
  1289.   return supports[aFeature];
  1290. }
  1291.  
  1292. /**
  1293.  * supportsSearch
  1294.  * @see flockIMediaWebService#supportsSearch
  1295.  */
  1296. flockFacebookService.prototype.supportsSearch =
  1297. function flockFacebookService_supportsSearch(aQueryString) {
  1298.   return false;
  1299. }
  1300.  
  1301. /**
  1302.  * upload2
  1303.  * @see flockIMediaWebService#upload
  1304.  */
  1305. flockFacebookService.prototype.upload =
  1306. function flockFacebookService_upload(aListener, aUpload, aFilename) {
  1307.   var params = CC[HASH_PROPERTY_BAG_CONTRACTID]
  1308.                .createInstance(CI.nsIWritablePropertyBag2);
  1309.   params.setPropertyAsAString("description", aUpload.description);
  1310.  
  1311.   this.api.uploadPhotosViaUploader(aListener, aFilename, params, aUpload);
  1312. }
  1313.  
  1314. /**
  1315.  * The following methods defined in flockIMediaWebService are handled by
  1316.  * component toolkit:
  1317.  *
  1318.  * @see flockIMediaWebService#checkIsStreamUrl
  1319.  * @see flockIMediaWebService#handlesMediaStream
  1320.  * @see flockIMediaWebService#getMediaQueryFromURL
  1321.  */
  1322.  
  1323. /**
  1324.  * decorateForMedia
  1325.  * @see flockIMediaWebService#decorateForMedia
  1326.  */
  1327. flockFacebookService.prototype.decorateForMedia =
  1328. function flockFacebookService_decorateForMedia(aDocument) {
  1329.   this._logger.debug(".decorateForMedia(aDocument)");
  1330.   var results = CC[HASH_PROPERTY_BAG_CONTRACTID]
  1331.                 .createInstance(CI.nsIWritablePropertyBag2);
  1332.   var inst = this;
  1333.   if (this.webDetective.detect("facebook", "person", aDocument, results) ||
  1334.       this.webDetective.detect("facebook", "photo", aDocument, results)) {
  1335.     var userId = results.getPropertyAsAString("userid");
  1336.     var myListener = {
  1337.       onSuccess: function listener_onSuccess(aEnum) {
  1338.         aEnum = aEnum.QueryInterface(CI.nsISimpleEnumerator);
  1339.         var person = aEnum.getNext()
  1340.                           .QueryInterface(CI.nsIWritablePropertyBag2);
  1341.         var username;
  1342.         try {
  1343.           username = person.getProperty("name");
  1344.         } catch (ex) {
  1345.           username = userId;
  1346.         }
  1347.  
  1348.         var mediaArr = [];
  1349.         var media = {
  1350.           name: username,
  1351.           query: "user:" + userId + "|username:" + username,
  1352.           label: username,
  1353.           favicon: inst.icon,
  1354.           service: inst.shortName
  1355.         }
  1356.         mediaArr.push(media);
  1357.         if (!aDocument._flock_decorations) {
  1358.           aDocument._flock_decorations = {};
  1359.         }
  1360.         aDocument._flock_decorations.mediaArr = mediaArr;
  1361.         inst.obs.notifyObservers(aDocument, "media", "media:update");
  1362.       }
  1363.     };
  1364.     this.api.usersGetInfo(myListener, userId, ["name"]);
  1365.   } else if (this.webDetective.detect("facebook",
  1366.                                       "tagged",
  1367.                                       aDocument,
  1368.                                       results))
  1369.   {
  1370.     var userId = results.getPropertyAsAString("userid");
  1371.     var myListener = {
  1372.       onSuccess: function listener_onSuccess(aEnum) {
  1373.         aEnum = aEnum.QueryInterface(CI.nsISimpleEnumerator);
  1374.         var person = aEnum.getNext()
  1375.                           .QueryInterface(CI.nsIWritablePropertyBag2);
  1376.         var username;
  1377.         try {
  1378.           username = person.getProperty("name");
  1379.         } catch (ex) {
  1380.           username = userId;
  1381.         }
  1382.  
  1383.         var label = flockGetString("photo/mediabar",
  1384.                                    "flock.media.simpleviewing.searchunique",
  1385.                                    [username]);
  1386.  
  1387.         var mediaArr = [];
  1388.         var media = {
  1389.           name: username,
  1390.           query: "searchunique:" + userId + "|username:" + username,
  1391.           label: label,
  1392.           favicon: inst.icon,
  1393.           service: inst.shortName
  1394.         }
  1395.         mediaArr.push(media);
  1396.         if (!aDocument._flock_decorations) {
  1397.           aDocument._flock_decorations = {};
  1398.         }
  1399.         aDocument._flock_decorations.mediaArr = mediaArr;
  1400.         inst.obs.notifyObservers(aDocument, "media", "media:update");
  1401.       }
  1402.     };
  1403.     this.api.usersGetInfo(myListener, userId, ["name"]);
  1404.   }
  1405. }
  1406.  
  1407.  
  1408. // Checks if the TEXTAREA is drag and droppable
  1409. flockFacebookService.prototype._isDnDableTextarea =
  1410. function ffs__isDnDableTextarea(aDocument, aXPath, aTextarea) {
  1411.   if (aDocument && aXPath && aTextarea) {
  1412.     var xpath = this.webDetective.getString("facebook", aXPath, "");
  1413.     var results = aDocument.evaluate(xpath, aDocument, null,
  1414.                                      CI.nsIDOMXPathResult.ANY_TYPE, null);
  1415.     if (results && results.iterateNext() == aTextarea) {
  1416.       return true;
  1417.     }
  1418.   }
  1419.  
  1420.   return false;
  1421. }
  1422.  
  1423.  
  1424. // Set the focus on the Wall TEXTAREA by sending a focus event.
  1425. // This action is only required to activate the field if the user has not yet
  1426. // typed anything in the Wall space.
  1427. flockFacebookService.prototype._setFocusOnWall =
  1428. function ffs__setFocusOnWall(aXpathField, aTextarea) {
  1429.   if (aTextarea && aXpathField && aXpathField == XPATH_WALLTEXT) {
  1430.     var doc = aTextarea.ownerDocument;
  1431.     if (doc instanceof CI.nsIDOMHTMLDocument) {
  1432.       var placeholder = this.webDetective.getString("facebook",
  1433.                                                     "wallPlaceholder",
  1434.                                                     "");
  1435.  
  1436.       // If the placeholder text is displaying, trigger the focus event to
  1437.       // activate the TEXTAREA
  1438.       if (aTextarea.value == aTextarea.getAttribute(placeholder)) {
  1439.         var eventObj = doc.createEvent("Events");
  1440.         eventObj.initEvent("focus", true, true);
  1441.         aTextarea.dispatchEvent(eventObj);
  1442.       }
  1443.     }
  1444.   }
  1445. }
  1446.  
  1447.  
  1448. // BEGIN flockIRichContentDropHandler Interface
  1449. // Handle the rich content drop
  1450. flockFacebookService.prototype.handleDrop =
  1451. function ffs_handleDrop(aFlavours, aTextarea) {
  1452.   this._logger.debug(".handleDrop()");
  1453.  
  1454.   var inst = this;
  1455.   var dropCallback = function facebook_dropCallback(aFlav) {
  1456.     var dataObj = {}, len = {};
  1457.     aFlavours.getTransferData(aFlav, dataObj, len);
  1458.  
  1459.     // Activate the TEXTAREA if we are dropping on the Wall
  1460.     inst._setFocusOnWall(XPATH_WALLTEXT, aTextarea);
  1461.  
  1462.     var caretPos = aTextarea.selectionEnd;
  1463.     var currentValue = aTextarea.value;
  1464.     // Add a trailing space so that Facebook scrapes for a valid,
  1465.     // unmodified url
  1466.     var nextChar = currentValue.charAt(caretPos);
  1467.     var trailingSpace = ((nextChar == "") ||
  1468.                          (nextChar == " ") ||
  1469.                          (nextChar == "\n"))
  1470.                       ? ""
  1471.                       : " ";
  1472.     // Only add a breadcrumb if the insertion point is at the end of
  1473.     // the text so that we don't duplicate breadcrumbs
  1474.     var breadcrumb = (aTextarea.value.length == caretPos)
  1475.                    ? CC[FLOCK_RDNDS_CONTRACTID]
  1476.                      .getService(CI.flockIRichDNDService)
  1477.                      .getBreadcrumb("plain")
  1478.                    : "";
  1479.  
  1480.     aTextarea.value = currentValue.substring(0, caretPos)
  1481.                     + dataObj.value.QueryInterface(CI.nsISupportsString)
  1482.                              .data.replace(/: /, ":\n")
  1483.                     + trailingSpace
  1484.                     + currentValue.substring(caretPos);
  1485.  
  1486.     // Call special Facebook hack to embed content into field
  1487.     _initKeyupEvent(aTextarea);
  1488.  
  1489.     aTextarea.value += breadcrumb;
  1490.   };
  1491.  
  1492.   return this._handleTextareaDrop(CATEGORY_ENTRY_NAME, this.fbService.domains,
  1493.                                   aTextarea, dropCallback);
  1494. }
  1495. // END flockIRichContentDropHandler interface
  1496.  
  1497.  
  1498. //**************************************
  1499. //* Facebook Account implementation
  1500. //**************************************
  1501.  
  1502. function flockFacebookAccount() {
  1503.   this.acUtils = CC[FLOCK_ACCOUNT_UTILS_CONTRACTID]
  1504.                  .getService(CI.flockIAccountUtils);
  1505.   this.service = CC[FACEBOOK_CONTRACTID]
  1506.                  .getService(CI.flockIWebService);
  1507.   this.api = CC["@flock.com/api/facebook;1"]
  1508.              .getService(CI.flockIFacebookAPI)
  1509.              .wrappedJSObject;
  1510.   this.faves_coop = CC[FLOCK_SINGLETON_CONTRACTID]
  1511.                     .getService(CI.flockISingleton)
  1512.                     .getSingleton(FAVES_COOP)
  1513.                     .wrappedJSObject;
  1514.   this.webDetective = CC["@flock.com/web-detective;1"]
  1515.                       .getService(CI.flockIWebDetective);
  1516.   this._ctk = {
  1517.     interfaces: [
  1518.       "nsISupports",
  1519.       "flockIWebServiceAccount",
  1520.       "flockISocialWebServiceAccount",
  1521.       "flockIFacebookAccount",
  1522.       "flockIMediaWebServiceAccount",
  1523.       "flockIMediaUploadAccount"
  1524.     ]
  1525.   };
  1526.   getCompTK().addAllInterfaces(this);
  1527.  
  1528.   this._logger = CC["@flock.com/logger;1"].createInstance(CI.flockILogger);
  1529.   this._logger.init("facebookAccount");
  1530. }
  1531.  
  1532. // BEGIN flockIWebServiceAccount interface
  1533. flockFacebookAccount.prototype.urn = "";
  1534. flockFacebookAccount.prototype.username = "";
  1535. flockFacebookAccount.prototype.status = "";
  1536.  
  1537. flockFacebookAccount.prototype.activate =
  1538. function flockFacebookAccount_activate(aListener) {
  1539.   this._logger.debug(".activate()");
  1540.   var acctCoopObj = this.faves_coop.get(this.urn);
  1541.  
  1542.   var inst = this;
  1543.   this.api.authenticate({
  1544.     onSuccess: function onSuccess() {
  1545.       inst._logger.debug(".activate() authenticated: " + inst.api.uid);
  1546.       if (aListener) {
  1547.         // Get the user's full name before popping the mediabar so the stream
  1548.         // name is correct.
  1549.         var usersGetInfoListener = {
  1550.           onSuccess: function ugil_onSuccess(aEnum, aStatus) {
  1551.             aEnum.QueryInterface(CI.nsISimpleEnumerator);
  1552.             if (aEnum.hasMoreElements()) {
  1553.               var bag = aEnum.getNext();
  1554.               bag.QueryInterface(CI.nsIPropertyBag);
  1555.               acctCoopObj.avatar = bag.getProperty("avatar");
  1556.               acctCoopObj.name = bag.getProperty("name");
  1557.               acctCoopObj.nextRefresh = new Date(0);
  1558.               acctCoopObj.isPollable = true;
  1559.               acctCoopObj.isAuthenticated = true;
  1560.               aListener.onSuccess(inst, "accountAuthorized");
  1561.             }
  1562.           },
  1563.           onError: function ugil_onError() {
  1564.             inst._logger.error("Error on usersGetInfo");
  1565.           }
  1566.         };
  1567.         inst.api.usersGetInfo(usersGetInfoListener, inst.api.uid, null);
  1568.       }
  1569.     },
  1570.     onError: function onError(aSubject, aTopic, aError) {
  1571.       inst._logger.error("error on authentication: " + aError.errorString);
  1572.     }
  1573.   });
  1574. }
  1575.  
  1576. flockFacebookAccount.prototype.login =
  1577. function flockFacebookAccount_login(aListener) {
  1578.   this._logger.debug(".login()");
  1579.   var inst = this;
  1580.   var myListener = {
  1581.     onSuccess: function listener_onSuccess() {
  1582.       inst.acUtils.ensureOnlyAuthenticatedAccount(inst.urn);
  1583.       // force refresh on login
  1584.       var pollerSvc = CC["@flock.com/poller-service;1"]
  1585.                       .getService(CI.flockIPollerService);
  1586.       pollerSvc.forceRefresh(inst.urn);
  1587.       if (aListener) {
  1588.         aListener.onSuccess(null, "");
  1589.       }
  1590.     },
  1591.     onError: function listener_onError(aFlockError) {
  1592.       if (aListener) {
  1593.         aListener.onError(null, null, aFlockError);
  1594.       }
  1595.     }
  1596.   }
  1597.   this.api.deauthenticate();
  1598.   this.acUtils.markAllAccountsAsLoggedOut(FACEBOOK_CONTRACTID);
  1599.  
  1600.   this.api.authenticate(myListener);
  1601. }
  1602. // END flockIWebServiceAccount interface
  1603.  
  1604. // BEGIN flockISocialWebServiceAccount interface
  1605. flockFacebookAccount.prototype.hasFriendActions = true;
  1606. flockFacebookAccount.prototype.isStatusSupported = true;
  1607. flockFacebookAccount.prototype.isStatusEditable  = true;
  1608. flockFacebookAccount.prototype.isPostLinkSupported = true;
  1609. flockFacebookAccount.prototype.isMyMediaFavoritesSupported = false;
  1610.  
  1611. flockFacebookAccount.prototype.setStatus =
  1612. function flockFacebookAccount_setStatus(aStatusMessage, aListener)
  1613. {
  1614.   this._logger.info(".setStatus('" + aStatusMessage + "')");
  1615.  
  1616.   var inst = this;
  1617.  
  1618.   var statusListener = {
  1619.     onSuccess: function onSuccessHandler(aResult, aTopic) {
  1620.       inst.faves_coop.get(inst.urn).statusMessage = aStatusMessage;
  1621.       if (aListener) {
  1622.         aListener.onSuccess(aResult, aTopic);
  1623.       }
  1624.     },
  1625.     onError: function onErrorHandler(aError) {
  1626.       if (aListener) {
  1627.         aListener.onError(aError);
  1628.       }
  1629.     }
  1630.   }
  1631.  
  1632.   this.api.setStatus(aStatusMessage, statusListener);
  1633. }
  1634.  
  1635. flockFacebookAccount.prototype.getEditableStatus =
  1636. function flockFacebookAccount_getEditableStatus()
  1637. {
  1638.   this._logger.info(".getEditableStatus()");
  1639.   return this.faves_coop.get(this.urn).statusMessage;
  1640. }
  1641.  
  1642. flockFacebookAccount.prototype.formatStatusForDisplay =
  1643. function flockFacebookAccount_formatStatusForDisplay(aStatusMessage)
  1644. {
  1645.   var sbs = CC["@mozilla.org/intl/stringbundle;1"]
  1646.             .getService(CI.nsIStringBundleService);
  1647.   var bundle = sbs.createBundle(FACEBOOK_PROPERTIES);
  1648.   return bundle.GetStringFromName("flock.facebook.status.prefix") + " "
  1649.          + aStatusMessage
  1650.          + (!aStatusMessage.match(/[.!?]+$/)
  1651.             ? bundle.GetStringFromName("flock.facebook.status.postfix") : "");
  1652. }
  1653.  
  1654. flockFacebookAccount.prototype.getMeNotifications =
  1655. function flockFacebookAccount_getMeNotifications()
  1656. {
  1657.   this._logger.info(".getMeNotifications()");
  1658.  
  1659.   var sbs = CC["@mozilla.org/intl/stringbundle;1"]
  1660.             .getService(CI.nsIStringBundleService);
  1661.   var bundle = sbs.createBundle(FACEBOOK_PROPERTIES);
  1662.  
  1663.   var noties = [];
  1664.   var inst = this;
  1665.   function _addNotie(aType, aCount) {
  1666.     var stringName = "flock.facebook.noties."
  1667.                    + aType + "."
  1668.                    + ((parseInt(aCount) <= 0) ? "none" : "some");
  1669.     noties.push({
  1670.       class: aType,
  1671.       tooltip: bundle.GetStringFromName(stringName),
  1672.       metricsName: aType,
  1673.       count: aCount,
  1674.       URL: inst.webDetective.getString("facebook", aType + "_URL", "")
  1675.     });
  1676.   }
  1677.   var c_acct = this.faves_coop.get(this.urn);
  1678.   _addNotie("meMessages", c_acct.accountMessages);
  1679.   _addNotie("mePokes", c_acct.fbPokes);
  1680.   _addNotie("meFriendRequests", c_acct.fbFriendRequests);
  1681.   _addNotie("meGroupRequests", c_acct.fbGroupInvites);
  1682.   _addNotie("meEventRequests", c_acct.fbEventInvites);
  1683.   return JSON.toString(noties);
  1684. }
  1685.  
  1686. flockFacebookAccount.prototype.markAllMeNotificationsSeen =
  1687. function flockFacebookAccount_markAllMeNotificationsSeen(aType) {
  1688.   this._logger.debug(".markAllMeNotificationsSeen('" + aType + "')");
  1689.   var c_acct = this.faves_coop.get(this.urn);
  1690.   switch (aType) {
  1691.     case "meMessages":
  1692.       c_acct.accountMessages = 0;
  1693.       break;
  1694.     case "mePokes":
  1695.       c_acct.fbPokes = 0;
  1696.       break;
  1697.     case "meFriendRequests":
  1698.       c_acct.fbFriendRequests = 0;
  1699.       break;
  1700.     case "meGroupRequests":
  1701.       c_acct.fbGroupInvites = 0;
  1702.       break;
  1703.     case "meEventRequests":
  1704.       c_acct.fbEventInvites = 0;
  1705.       break;
  1706.     default:
  1707.       break;
  1708.   }
  1709. }
  1710.  
  1711. flockFacebookAccount.prototype.getFriendActions =
  1712. function flockFacebookAccount_getFriendActions(aFriendURN)
  1713. {
  1714.   this._logger.info(".getFriendActions('" + aFriendURN + "')");
  1715.  
  1716.   var actionNames = ["friendPoke",
  1717.                      "friendMessage",
  1718.                      "friendWallPost",
  1719.                      "friendShareLink",
  1720.                      "friendViewProfile",
  1721.                      "friendGiveGift",
  1722.                      "friendShareFlock"];
  1723.  
  1724.   var sbs = CC["@mozilla.org/intl/stringbundle;1"]
  1725.             .getService(CI.nsIStringBundleService);
  1726.   var bundle = sbs.createBundle(FACEBOOK_PROPERTIES);
  1727.  
  1728.   var actions = [];
  1729.   var c_friend = this.faves_coop.get(aFriendURN);
  1730.   if (c_friend) {
  1731.     var c_acct = this.faves_coop.get(this.urn);
  1732.     for each (var i in actionNames) {
  1733.       actions.push({
  1734.         label: bundle.GetStringFromName("flock.facebook.actions." + i),
  1735.         class: i,
  1736.         spec: this.webDetective.getString("facebook", i, "")
  1737.                   .replace("%accountid%", c_acct.accountId)
  1738.                   .replace("%friendid%", c_friend.accountId)
  1739.       });
  1740.     }
  1741.   }
  1742.   return JSON.toString(actions);
  1743. }
  1744.  
  1745. flockFacebookAccount.prototype.getSharingAction =
  1746. function flockFacebookAccount_getSharingAction(aFriendURN, aTransferable)
  1747. {
  1748.   this._logger.info(".getSharingAction('" + aFriendURN + "'," +
  1749.                                       "'" + aTransferable + "')");
  1750.  
  1751.   // We want to take advantage of Facebook's "Share Link" feature so we try to
  1752.   // incorporate it for every object we share.
  1753.   return "method:flockIFacebookAccount:sendMessageWithSharedLink";
  1754. }
  1755.  
  1756. flockFacebookAccount.prototype.getPostLinkAction =
  1757. function flockFacebookAccount_getPostLinkAction(aTransferable)
  1758. {
  1759.   // We need to add the breadcrumb so we'll handle this in the postLink method
  1760.   return "method:flockIFacebookAccount:postLink";
  1761. }
  1762.  
  1763. flockFacebookAccount.prototype.getProfileURLForFriend = 
  1764. function flockFacebookAccount_getProfileURLForFriend(aFriendURN)
  1765. {
  1766.   this._logger.info(".getProfileURLForFriend('" + aFriendURN + "')");
  1767.  
  1768.   var url = "";
  1769.   var c_friend = this.faves_coop.get(aFriendURN);
  1770.   if (c_friend) {
  1771.     url = this.webDetective.getString("facebook", "friendprofile", "")
  1772.                            .replace("%accountid%", c_friend.accountId);
  1773.   }
  1774.  
  1775.   return url;
  1776. }
  1777.  
  1778. // END flockISocialWebServiceAccount interface
  1779.  
  1780. // BEGIN flockIFacebookAccount interface
  1781. flockFacebookAccount.prototype.sendMessageWithSharedLink =
  1782. function flockFacebookAccount_sendMessageWithSharedLink(aFriendURN,
  1783.                                                         aTransferable)
  1784. {
  1785.   this._logger.info(".sendMessageWithSharedLink('" + aFriendURN + "'," +
  1786.                                                "'" + aTransferable + "')");
  1787.  
  1788.   var msgURL = "";
  1789.  
  1790.   if (!aTransferable) {
  1791.     // Was not a DND but a programmatical call
  1792.     // Special case requires us to get the active browser tab and then use
  1793.     // that as the sharing URL
  1794.     var win = _wm.getMostRecentWindow("navigator:browser");
  1795.     if (win) {
  1796.       var currentURL = win.gBrowser.currentURI.spec;
  1797.       var subject = win.gBrowser.contentTitle;
  1798.       var body = currentURL; 
  1799.       var c_friend = this.faves_coop.get(aFriendURN);
  1800.  
  1801.       msgURL = this._buildSendMessageURL(c_friend.accountId, subject, body);
  1802.     }
  1803.   } else {
  1804.     msgURL = this._getSendMessageURLFromTransferable(aFriendURN,
  1805.                                                      aTransferable);
  1806.   }
  1807.  
  1808.   if (msgURL) {
  1809.     this._loadNewTabWithCallback(msgURL, this._sharedLinkCallback);
  1810.   }
  1811. }
  1812.  
  1813. flockFacebookAccount.prototype.shareFlock =
  1814. function flockFacebookAccount_shareFlock(aFriendURN)
  1815. {
  1816.   this._logger.info(".shareFlock('" + aFriendURN + "')");
  1817.  
  1818.   // Get friend
  1819.   var c_friend = this.faves_coop.get(aFriendURN);
  1820.   if (c_friend) {
  1821.     // Get "Share Flock" message text
  1822.     var sbs = CC["@mozilla.org/intl/stringbundle;1"]
  1823.               .getService(CI.nsIStringBundleService);
  1824.     var bundle = sbs.createBundle(FACEBOOK_PROPERTIES);
  1825.     var subject = bundle.GetStringFromName(FACEBOOK_SHARE_FLOCK_SUBJECT);
  1826.     try {
  1827.       var body = bundle.GetStringFromName(FACEBOOK_SHARE_FLOCK_MESSAGE + "0");
  1828.       for (var i = 1; ; i++) {
  1829.         body += "\n" + bundle.GetStringFromName(FACEBOOK_SHARE_FLOCK_MESSAGE + i);
  1830.       }
  1831.     } catch (ex) {
  1832.       // Ignore -- we've hit the end of our message lines
  1833.     }
  1834.     // Build the URL and send it to the friend
  1835.     var msgURL = this._buildSendMessageURL(c_friend.accountId, subject, body);
  1836.     this._loadNewTabWithCallback(msgURL, this._shareFlockCallback);
  1837.   }
  1838. }
  1839.  
  1840. flockFacebookAccount.prototype.postLink =
  1841. function flockFacebookAccount_postLink(aTransferable)
  1842. {
  1843.   var url;
  1844.  
  1845.   if (aTransferable) {
  1846.     // Something was dropped onto the "Post Link" button: get the URL from the
  1847.     // transferable
  1848.  
  1849.     // This flavor set will ignore a text selection, which is OK
  1850.     var flavors = ["text/x-flock-media",
  1851.                    "text/x-moz-url"];
  1852.     var message = CC[FLOCK_RDNDS_CONTRACTID]
  1853.                   .getService(CI.flockIRichDNDService)
  1854.                   .getMessageFromTransferable(aTransferable,
  1855.                                               flavors.length,
  1856.                                               flavors);
  1857.     url = message.body;
  1858.   } else {
  1859.     // The "Post Link" button was clicked: get the current tab's URL
  1860.     var win = _wm.getMostRecentWindow("navigator:browser");
  1861.     if (win) {
  1862.       url = win.gBrowser.currentURI.spec;
  1863.     }
  1864.   }
  1865.  
  1866.   if (url) {
  1867.     // Post it onto this user's profile
  1868.     var postURL = this.webDetective.getString("facebook", "postLink", "")
  1869.                                    .replace("%url%", encodeURIComponent(url));
  1870.     if (postURL) {
  1871.       this._loadNewTabWithCallback(postURL, this._postLinkCallback);
  1872.     }
  1873.   }
  1874. }
  1875.  
  1876. flockFacebookAccount.prototype._getSendMessageURLFromTransferable =
  1877. function flockFacebookAccount__getSendMessageURLFromTransferable(aFriendURN,
  1878.                                                                  aTransferable)
  1879. {
  1880.   this._logger.info("._getSendMessageURLFromTransferable('" + aFriendURN + "'," +
  1881.                                                         "'" + aTransferable + "')");
  1882.  
  1883.   var messageURL = "";
  1884.   var c_friend = this.faves_coop.get(aFriendURN);
  1885.   if (c_friend) {
  1886.     // Flavors we want to support, in order of preference
  1887.     var flavors = ["text/x-flock-media",
  1888.                    "text/x-moz-url",
  1889.                    "text/unicode",
  1890.                    "text/html"];
  1891.  
  1892.     var message = CC[FLOCK_RDNDS_CONTRACTID]
  1893.                   .getService(CI.flockIRichDNDService)
  1894.                   .getMessageFromTransferable(aTransferable,
  1895.                                               flavors.length,
  1896.                                               flavors);
  1897.     if (message.body) {
  1898.       messageURL = this._buildSendMessageURL(c_friend.accountId,
  1899.                                              message.subject,
  1900.                                              message.body);
  1901.     }
  1902.   }
  1903.  
  1904.   return messageURL;
  1905. }
  1906.  
  1907. flockFacebookAccount.prototype._buildSendMessageURL =
  1908. function flockFacebookAccount__buildSendMessageURL(aFriendId, aSubject, aBody) {
  1909.   return this.webDetective.getString("facebook", "sendMessage", "")
  1910.              .replace("%friendid%", aFriendId)
  1911.              .replace("%subject%", encodeURIComponent(aSubject))
  1912.              .replace("%message%", encodeURIComponent(aBody));
  1913. }
  1914.  
  1915. // This opens a new tab and loads aURL into it. After the tab has loaded, the
  1916. // aCallback method is called.
  1917. flockFacebookAccount.prototype._loadNewTabWithCallback =
  1918. function flockFacebookAccount__loadNewTabWithCallback(aUrl, aCallback)
  1919. {
  1920.   var win = _wm.getMostRecentWindow("navigator:browser");
  1921.   if (win) {
  1922.     var browser = win.getBrowser();
  1923.     var newTab = browser.loadOneTab(aUrl, null, null, null, false, false);
  1924.  
  1925.     var obs = CC["@mozilla.org/observer-service;1"]
  1926.               .getService(CI.nsIObserverService);
  1927.  
  1928.     var observer = {
  1929.       observe: function loadNewTabWithCallback_observer(aContent,
  1930.                                                         aTopic,
  1931.                                                         aOwnsWeak)
  1932.       {
  1933.         var contentWindow = newTab.linkedBrowser.docShell
  1934.                                   .QueryInterface(CI.nsIInterfaceRequestor)
  1935.                                   .getInterface(CI.nsIDOMWindow);
  1936.  
  1937.         if (contentWindow == aContent) {
  1938.           obs.removeObserver(this, aTopic);
  1939.  
  1940.           // Callback?
  1941.           if (aCallback) {
  1942.             aCallback(contentWindow);
  1943.           }
  1944.         }
  1945.       }
  1946.     }
  1947.  
  1948.     obs.addObserver(observer, "EndDocumentLoad", false);
  1949.   }
  1950. }
  1951.  
  1952. // Callbacks for _loadNewTabWithCallback
  1953.  
  1954. // Adds the breadcrumb to the post link comment field.
  1955. flockFacebookAccount.prototype._postLinkCallback =
  1956. function flockFacebookAccount__postLinkCallback(aWindow) {
  1957.   // TEXTAREA field on post link page is called "sharer_popup_message"
  1958.   var formField = aWindow.document.getElementById("sharer_popup_message");
  1959.   if (formField) {
  1960.     // Add breadcrumb
  1961.     _addBreadcrumb(formField);
  1962.  
  1963.     // Put the cursor at the top of the text area, above breadcrumb
  1964.     formField.setSelectionRange(0, 0);
  1965.   }
  1966. }
  1967.  
  1968. // Embeds the shared link into the content of the message and then adds the
  1969. // breadcrumb.
  1970. flockFacebookAccount.prototype._sharedLinkCallback =
  1971. function flockFacebookAccount__sharedLinkCallback(aWindow) {
  1972.   // TEXTAREA field on send message page is called "message"
  1973.   var formField = aWindow.document.getElementById("message");
  1974.   if (formField) {
  1975.     // Call special Facebook hack to embed content into field
  1976.     _initKeyupEvent(formField);
  1977.  
  1978.     // Add breadcrumb
  1979.     _addBreadcrumb(formField);
  1980.   }
  1981. }
  1982.  
  1983. // Embeds the Flock link into the content of the message, cleans up the text,
  1984. // and then moves the cursor to the top of the message.
  1985. flockFacebookAccount.prototype._shareFlockCallback =
  1986. function flockFacebookAccount__shareFlockCallback(aWindow) {
  1987.   // TEXTAREA field on send message page is called "message"
  1988.   var formField = aWindow.document.getElementById("message");
  1989.   if (formField) {
  1990.     // Call special Facebook hack to embed content into field
  1991.     _initKeyupEvent(formField);
  1992.  
  1993.     // Facebook converts newlines on the command line to breaks in the message,
  1994.     // which looks bad, so convert them back to newlines in the message.
  1995.     formField.value = formField.value.replace("<br />", "\n", "gi");
  1996.  
  1997.     // Put the cursor at the top of the text area, above our footer
  1998.     formField.setSelectionRange(0, 0);
  1999.   }
  2000. }
  2001.  
  2002. // END flockIFacebookAccount interface
  2003.  
  2004. //******************************************
  2005. //* XPCOM registration
  2006. //******************************************
  2007.  
  2008. function createModule(aParams) {
  2009.   return {
  2010.     registerSelf: function registerSelf(aCompMgr,
  2011.                                         aFileSpec,
  2012.                                         aLocation,
  2013.                                         aType) {
  2014.       aCompMgr.QueryInterface(CI.nsIComponentRegistrar);
  2015.       aCompMgr.registerFactoryLocation(aParams.CID,
  2016.                                        aParams.componentName,
  2017.                                        aParams.contractID,
  2018.                                        aFileSpec,
  2019.                                        aLocation,
  2020.                                        aType);
  2021.       var catMgr = CC[CATEGORY_MANAGER_CONTRACTID].
  2022.                    getService(CI.nsICategoryManager);
  2023.       if (!aParams.categories) {
  2024.         aParams.categories = [];
  2025.       }
  2026.       for (var i = 0; i < aParams.categories.length; i++) {
  2027.         var cat = aParams.categories[i];
  2028.         catMgr.addCategoryEntry(cat.category,
  2029.                                 cat.entry,
  2030.                                 cat.value,
  2031.                                 true,
  2032.                                 true);
  2033.       }
  2034.     },
  2035.     getClassObject: function getClassObject(aCompMgr, aCID, aIID) {
  2036.       if (!aCID.equals(aParams.CID)) {
  2037.         throw CR.NS_ERROR_NO_INTERFACE;
  2038.       }
  2039.       if (!aIID.equals(CI.nsIFactory)) {
  2040.         throw CR.NS_ERROR_NOT_IMPLEMENTED;
  2041.       }
  2042.       return { // Factory
  2043.         createInstance: function createInstance(aOuter, aIID) {
  2044.           if (aOuter != null) {
  2045.             throw CR.NS_ERROR_NO_AGGREGATION;
  2046.           }
  2047.           var comp = new aParams.componentClass();
  2048.           if (aParams.implementationFunc) {
  2049.             aParams.implementationFunc(comp);
  2050.           }
  2051.           return comp.QueryInterface(aIID);
  2052.         }
  2053.       };
  2054.     },
  2055.     canUnload: function canUnload(aCompMgr) {
  2056.       return true;
  2057.     }
  2058.   };
  2059. }
  2060.  
  2061. // NS Module entrypoint
  2062. function NSGetModule(aCompMgr, aFileSpec) {
  2063.   return createModule({
  2064.     componentClass: flockFacebookService,
  2065.     CID: FACEBOOK_CID,
  2066.     contractID: FACEBOOK_CONTRACTID,
  2067.     componentName: CATEGORY_COMPONENT_NAME,
  2068.     implementationFunc: function implementationFunc(aComp) {
  2069.       getCompTK().addAllInterfaces(aComp);
  2070.     },
  2071.     categories: [
  2072.       {
  2073.         category: "wsm-startup",
  2074.         entry: CATEGORY_COMPONENT_NAME,
  2075.         value: FACEBOOK_CONTRACTID
  2076.       },
  2077.       {
  2078.         category: "flockWebService",
  2079.         entry: CATEGORY_ENTRY_NAME,
  2080.         value: FACEBOOK_CONTRACTID
  2081.       },
  2082.       {
  2083.         category: "flockMediaProvider",
  2084.         entry: CATEGORY_ENTRY_NAME,
  2085.         value: FACEBOOK_CONTRACTID
  2086.       },
  2087.       {
  2088.         category: FLOCK_RICH_CONTENT_CATEGORY_ENTRY,
  2089.         entry: CATEGORY_ENTRY_NAME,
  2090.         value: FACEBOOK_CONTRACTID
  2091.       }
  2092.     ]
  2093.   });
  2094. }
  2095.